﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using PetriNetSharp.View;
using PetriNetSharp.Engine;
using System.Diagnostics;
using System.Windows.Data;
using System.Windows;
using PetriNetSharp.Converter;
using System.Windows.Controls;
using PetriNetSharp.CustomUIControls;

namespace PetriNetSharp.Controller
{
	/// <summary>
	/// The controller class that connects the Place model and the Place view
	/// </summary>
	public class PlaceController : IControllerInvalidatable
	{

		/// <summary>
		/// The view this is controlling
		/// </summary>
		private PlaceView view;

		/// <summary>
		/// The model this is controlling
		/// </summary>
		private Place model;

		/// <summary>
		/// The context menu for the view when it is being modified
		/// </summary>
		private ContextMenu modifyContextMenu;

		/// <summary>
		/// The delete menu item for the view's modify context menu
		/// </summary>
		private MenuItem deleteMenuItem;

		/// <summary>
		/// The max token slider for the view's modify context menu
		/// </summary>
		private Slider maxTokenSlider;

		/// <summary>
		/// The tool tip for the PlaceView
		/// </summary>
		private SharpToolTip toolTip;

		#region value converters

		/// <summary>
		/// A converter for the location of the place
		/// </summary>
		private PointToPlaceLocationValueConverter locationConverter = 
			new PointToPlaceLocationValueConverter();

		/// <summary>
		/// A converter for use with the tool tip text
		/// </summary>
		private PlaceToToolTipTextValueConverter toolTipTextValueConverter = 
			new PlaceToToolTipTextValueConverter();

		/// <summary>
		/// A converter for use with the tool tip header
		/// </summary>
		private PlaceToToolTipHeaderValueConverter toolTipHeaderValueConverter = 
			new PlaceToToolTipHeaderValueConverter();

		/// <summary>
		/// A converter for use with the is enabled properties with IsRunning
		/// </summary>
		private InvertBooleanValueConverter isEnabledValueConverter = 
			new InvertBooleanValueConverter();

		#endregion

		/// <summary>
		/// Create a new PlaceController
		/// </summary>
		/// <param name="view">The view this will control</param>
		/// <param name="model">The model this will control</param>
		/// <preconditions>
		/// view cannot be null
		/// model cannot be null
		/// </preconditions>
		public PlaceController(PlaceView view, Place model)
		{
			#region preconditions
			Debug.Assert(view != null, "PlaceController parameter view cannot be null", "The parameter view for PlaceController cannot be null for the constructor");
			Debug.Assert(model != null, "PlaceController parameter model cannot be null", "The parameter model for PlaceController cannot be null for the constructor");
			#endregion

			modifyContextMenu = new ContextMenu();

			//Set up maxTokenSlider to allow the user to change the max token size
			maxTokenSlider = new Slider();
			maxTokenSlider.Minimum = 1.0;
			maxTokenSlider.Maximum = Place.DEFAULT_MAX_TOKENS;
			maxTokenSlider.SmallChange = 1.0;
			maxTokenSlider.TickFrequency = 1.0;
			maxTokenSlider.IsSnapToTickEnabled = true;
			maxTokenSlider.TickPlacement = System.Windows.Controls.Primitives.TickPlacement.BottomRight;
			maxTokenSlider.AutoToolTipPlacement = System.Windows.Controls.Primitives.AutoToolTipPlacement.BottomRight;
			SharpToolTip maxToolTip = new SharpToolTip();
			maxToolTip.Header = "Max Tokens";
			maxToolTip.Text = "Use this slider to set the maximum amount of tokens this place can hold at one time.";
			maxTokenSlider.ToolTip = maxToolTip;
			ToolTipService.SetShowOnDisabled(maxTokenSlider, true);

			modifyContextMenu.Items.Add(maxTokenSlider);

			modifyContextMenu.Items.Add(new Separator());


			//Set up the delete menu item to allow the user to delete the plac
			deleteMenuItem = new MenuItem();
			deleteMenuItem.Header = "Delete Place";
			deleteMenuItem.Click += (object sender, RoutedEventArgs e) =>
				{
					Model.Invalidate();
				};
			SharpToolTip deleteToolTip = new SharpToolTip();
			deleteToolTip.Header = "Delete Place";
			deleteToolTip.Text = "Click this to delete this place from the Petri Net.";
			deleteMenuItem.ToolTip = deleteToolTip;
			modifyContextMenu.Items.Add(deleteMenuItem);

			



			toolTip = new SharpToolTip();


			this.view = view;
			this.model = model;

			RemoveConnections();
			SetBindings();
		}

		/// <summary>
		/// Get or set the model
		/// </summary>
		/// <preconditions>
		/// On set: model cannot be null
		/// </preconditions>
		public Place Model
		{
			get { return model; }
			set
			{
				#region preconditions
				Debug.Assert(value != null, "Model cannot be null",
					"The PlaceController was passed a null value for its model");
				#endregion

				RemoveConnections();
				model = value;
				RemoveConnections();
				SetBindings();
			}
		}

		/// <summary>
		/// Get or set the view
		/// </summary>
		/// <preconditions>
		/// On set: value cannot be null
		/// </preconditions>
		public PlaceView View
		{
			get { return view; }
			set
			{
				#region preconditions
				Debug.Assert(value != null, "View cannot be null", 
					"The PlaceController was passed a null value for its view");
				#endregion	

				RemoveConnections();
				view = value;
				RemoveConnections();
				SetBindings();
			}
		}

		#region add and remove connections between model and view

		/// <summary>
		/// Remove the connections between the view and model
		/// </summary>
		private void RemoveConnections()
		{
			//Remove bindings
			if(BindingOperations.IsDataBound(View, PlaceView.TokenProperty))
				BindingOperations.ClearBinding(View, PlaceView.TokenProperty);
			
			if(BindingOperations.IsDataBound(View, PlaceView.PlaceNameProperty))
				BindingOperations.ClearBinding(View, PlaceView.PlaceNameProperty);
			
			if(BindingOperations.IsDataBound(View, Canvas.TopProperty))
				BindingOperations.ClearBinding(View, Canvas.TopProperty);
			
			if(BindingOperations.IsDataBound(View, Canvas.LeftProperty))
				BindingOperations.ClearBinding(View, Canvas.LeftProperty);
			
			if(BindingOperations.IsDataBound(View, PlaceView.PlaceNameIsEnabledProperty))
				BindingOperations.ClearBinding(View, PlaceView.PlaceNameIsEnabledProperty);

			if(BindingOperations.IsDataBound(View, PlaceView.TokenIsEnabledProperty))
				BindingOperations.ClearBinding(View, PlaceView.TokenIsEnabledProperty);

			if (BindingOperations.IsDataBound(View, PlaceView.MaxTokenProperty))
				BindingOperations.ClearBinding(View, PlaceView.MaxTokenProperty);

			BindingOperations.ClearBinding(maxTokenSlider, Slider.ValueProperty);

			//Remove the View's context menu
			View.ContextMenu = null;

			//Remove the view's tool tip
			View.ToolTip = null;

			Model.RunningTokensChanged -= UpdateToolTipText;
			Model.StartingTokensChanged -= UpdateToolTipText;
			Model.MaxTokensChanged -= UpdateToolTipText;

			//Remove the Model's invalidation and IsRunning listener
			Model.Invalidated -= Invalidate;
			Model.IsRunningChanged -= IsRunningChangedListener;
		}

		/// <summary>
		/// Sets the connections between the view and the model
		/// </summary>
		private void SetBindings()
		{
			//Add view's place name binding
			Binding bind = new Binding();
			bind.Source = Model;
			bind.Path = new PropertyPath("Name");
			bind.Mode = BindingMode.TwoWay;
			bind.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
			View.SetBinding(PlaceView.PlaceNameProperty, bind);

			//Set the token bindings
			SetTokenBinding();
			bind = new Binding();
			bind.Source = Model;
			bind.Path = new PropertyPath("MaxTokens");
			bind.Mode = BindingMode.TwoWay;
			View.SetBinding(PlaceView.MaxTokenProperty, bind);

			bind = new Binding();
			bind.Source = Model;
			bind.Path = new PropertyPath("MaxTokens");
			bind.Mode = BindingMode.TwoWay;
			maxTokenSlider.SetBinding(Slider.ValueProperty, bind);

			//Set location bindings
			bind = new Binding();
			bind.Source = Model;
			bind.Path = new PropertyPath("Location");
			bind.Converter = locationConverter;
			bind.ConverterParameter = PointToDoubleValueConverterParams.X;
			View.SetBinding(Canvas.LeftProperty, bind);

			bind = new Binding();
			bind.Source = Model;
			bind.Path = new PropertyPath("Location");
			bind.Converter = locationConverter;
			bind.ConverterParameter = PointToDoubleValueConverterParams.Y;
			View.SetBinding(Canvas.TopProperty, bind);

			View.ContextMenu = modifyContextMenu;

			//Add is enabled bindings
			bind = new Binding();
			bind.Source = Model;
			bind.Path = new PropertyPath("IsRunning");
			bind.Converter = isEnabledValueConverter;
			View.SetBinding(PlaceView.TokenIsEnabledProperty, bind);

			bind = new Binding();
			bind.Source = Model;
			bind.Path = new PropertyPath("IsRunning");
			bind.Converter = isEnabledValueConverter;
			View.SetBinding(PlaceView.PlaceNameIsEnabledProperty, bind);

			//Set up the tool tip
			View.ToolTip = toolTip;
			bind = new Binding();
			bind.Source = Model;
			bind.Path = new PropertyPath("Name");
			bind.Converter = toolTipHeaderValueConverter;
			toolTip.SetBinding(SharpToolTip.HeaderProperty, bind);

			UpdateToolTipText();
			Model.RunningTokensChanged += UpdateToolTipText;
			Model.StartingTokensChanged += UpdateToolTipText;
			Model.MaxTokensChanged += UpdateToolTipText;

			//Attach the Model's invalidation and IsRunning listener
			Model.Invalidated += Invalidate;
			Model.IsRunningChanged += IsRunningChangedListener;

		
			
		}

		/// <summary>
		/// Right now just sets binding to be starting tokens
		/// </summary>
		/// <todo>
		/// After add running start have it make a choice to set to either
		/// starting or running tokens
		/// </todo>
		private void SetTokenBinding()
		{
			Binding bind = new Binding();
			bind.Source = Model;

			if (Model.IsRunning)
				bind.Path = new PropertyPath("RunningTokens");
			else
				bind.Path = new PropertyPath("StartingTokens");

			bind.Mode = BindingMode.TwoWay;
			View.SetBinding(PlaceView.TokenProperty, bind);
		}

		/// <summary>
		/// Listens for when IsRunningChanges and updates the View
		/// </summary>
		private void IsRunningChangedListener()
		{
			SetTokenBinding();

			if (Model.IsRunning)
			{
				View.ContextMenu = null;
			}
			else
			{
				View.ContextMenu = modifyContextMenu;
			}
		}

		/// <summary>
		/// Updates the tooltip to show the latest amount
		/// of tokens and max tokens
		/// </summary>
		private void UpdateToolTipText()
		{
			string text = "Tokens: ";
			if (Model.IsRunning)
				text += Model.RunningTokens.ToString();
			else
				text += Model.StartingTokens.ToString();

			text += Environment.NewLine;

			text += String.Format("Max Tokens: {0}" + Environment.NewLine, Model.MaxTokens.ToString());

			toolTip.Text = text;

		}

		#endregion

		#region move functionality

		/// <summary>
		/// Whether the Place is currently moving
		/// </summary>
		private bool isMoving;

		/// <summary>
		/// The last point to move against
		/// </summary>
		private Point lastPoint;

		/// <summary>
		/// Start the moving functionality
		/// </summary>
		/// <param name="point">The point to start the moving from</param>
		public void StartMove(Point point)
		{
			if (!Model.IsRunning)
			{
				isMoving = true;
				lastPoint = point;
				View.CaptureMouse();
			}
		}

		/// <summary>
		/// Move the Place
		/// </summary>
		/// <param name="point">The new point in the moving operation</param>
		public void MovePlace(Point point)
		{
			if (isMoving)
			{
				double diffX = point.X - lastPoint.X;
				double diffY = point.Y - lastPoint.Y;
				lastPoint = point;


				Point newPoint = new Point(Model.Location.X + diffX,
				    Model.Location.Y + diffY);
				Model.Location = newPoint;
			}
		}

		/// <summary>
		/// Ends the moving funcitonality
		/// </summary>
		public void EndMove()
		{
			isMoving = false;
			View.ReleaseMouseCapture();
		}


		#endregion

		#region IControllerInvalidatable Members

		/// <summary>
		/// Occurs when this PlaceController is invalidated
		/// </summary>
		public event ControllerInvalidatedEventHandler Invalidated;

		/// <summary>
		/// Invalidate this PlaceController
		/// </summary>
		public void Invalidate()
		{
			if (Invalidated != null)
				Invalidated(this);
		}

		#endregion
	}
}
